// =======================================================================
// 
//       Filename:  plodder.cpp
// 
//    Description:  
// 
//        Version:  1.0
//        Created:  27.03.2010 23:16:24
//       Revision:  none
//       Compiler:  g++
// 
//         Author:  Dominik 'domachine' Burgdörfer (-), dominik.burgdoerfer@googlemail.com
//        Company:  -
// 
// =======================================================================

#include "plodder.hpp"
#include "processor.hpp"
#include "pluginmanager.hpp"
#include "page/page.hpp"
#include "page/parser.hpp"
#include "utils/comparenodes.hpp"
#include "templatefile/file.hpp"
#include "exceptions/syntaxerror.hpp"
#include "exceptions/systemerror.hpp"
#include "exceptions/fileerror.hpp"
#include "navigation/node.hpp"
#include "utils/pathlist.hpp"
#include <string.h>
#include <vector>
#include <boost/regex.hpp>
#include <unistd.h>
#include <fstream>
#include <cerrno>
#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>

#define foreach BOOST_FOREACH

namespace fs = boost::filesystem;
using namespace std;
using namespace boost;
using namespace webplodder::exceptions;

namespace webplodder {
    Plodder::Plodder(const fs::path& configDir)
        : m_configDir(configDir), 
          m_cacheMode(templatefile::Parser::CACHE_ALL)
    {

    }

    Plodder::~Plodder() {
    }

    void Plodder::init()
    {
        fs::path configFile = m_configDir / "config.yaml";

        // Open config file and parse it
        ifstream in(configFile.string().c_str());
        if(in.good()) {
            YAML::Parser parser(in);

            YAML::Node doc;

            parser.GetNextDocument(doc);
            YAML::Iterator it;

            string key;

            for(it = doc.begin(); it != doc.end(); ++it) {
                it.first() >> key;
                it.second() >> m_scriptConfig[key];
            }

            in.close();
        }

        // Pass the parsed config to the Processor
        m_processor = shared_ptr<Processor>(
                new Processor(m_scriptConfig));
        shared_ptr<PluginManager> pluginManagerPtr(
                new PluginManager(m_configDir / "plugins"));
        
        pluginManagerPtr->scanPlugins();
        m_processor->setParent(this);
        m_processor->installPluginManager(
                                pluginManagerPtr);
    }

    void Plodder::scanDir(const fs::path& dir,
                          utils::PathList& exclude) 
    {
        m_targetPath = dir;
        if(chdir(dir.string().c_str()) < 0)
            throw SystemError("chdir failed for: " + dir.string());

        // Open the given directory and search it for
        // page files
        fs::recursive_directory_iterator dIter(".");
        fs::recursive_directory_iterator endIter;
        vector<page::Page> pageFiles;
        fs::path templateFile;
        vector<navigation::Node> navi;
        page::Parser pageParser;

        for(; dIter != endIter; ++dIter) {
            if(fs::is_regular_file(dIter->status()) &&
                    dIter->path().extension() == ".page" &&
                    (!exclude.contains(dIter->path()))) {
                fs::path outFile(dIter->path());
                outFile.replace_extension("html");
                page::Page pageFile(dIter->path(), 
                        outFile);

                try {
                    // Parse the current page file ...
                    pageParser.parse(pageFile);
                }
                catch(FileError& e) {
                    throw SystemError(e.what() + ": " + 
                                           formatPath(e.file()));
                }
                catch(YAML::Exception& e) {
                    throw ParsingError(formatPath(pageFile.sourcePath()),
                                       e.what());
                }

                if(pageFile.inMenu())
                    navi.push_back(navigation::Node(pageFile["title"],
                                        pageFile.outputPath().string(),
                                        pageFile.pos()));

                pageFiles.push_back(pageFile);
            }
            else if(fs::is_regular_file(dIter->status()) &&
                    dIter->path().filename() == "default.template" &&
                    dIter->path().parent_path().filename() == "." &&
                    (!exclude.contains(dIter->path())))
            {
                templateFile = dIter->path();
            }
            else if(fs::is_directory(dIter->status()) &&
                    exclude.contains(dIter->path())) {
                dIter.no_push();
            }
        }

        if(templateFile.empty())
            throw Exception("no template file found");

        if(pageFiles.empty())
            throw Exception("no page files found");

        // Sort navigation
        sort(navi.begin(),
                navi.end(), utils::compareNodes);

        // Install all navigations
        foreach(page::Page& file, pageFiles)
            file.installNavigation(navi);

        // Construct a templatefile::File object
        // upon the source file and parse it
        templatefile::File cTemplateFile(templateFile);
        templatefile::Parser parser;

        parser.setCacheMode(m_cacheMode);

        try {
            parser.parse(cTemplateFile);
        }
        catch(SyntaxError& e) {
            throw SyntaxError(e.what(), e.line(),
                    formatPath(e.file()));
        }

        m_processor->setTemplateFile(cTemplateFile);

        // Iterate through the page queue
        foreach(page::Page pageFile, pageFiles)
        {
            // ... and pass it to the Processor
            try {
                m_processor->process(pageFile);
            }
            catch(FileError& e) {
                throw SystemError(e.what() + ": " + 
                                    formatPath(e.file()),
                                  e.errNo());
            }
        }
    }

    void Plodder::setCacheMode(templatefile::Parser::CacheMode cacheMode)
    {
        m_cacheMode = cacheMode;
    }

    string Plodder::formatPath(const fs::path& filePath) {
        fs::path printablePath(m_targetPath);
        fs::path::iterator filePathIter = filePath.begin();

        if(filePathIter == filePath.end())
            return "";

        ++filePathIter;
        
        for(; filePathIter != filePath.end(); ++filePathIter)
            printablePath /= *filePathIter;

        return printablePath.string();
    }

    void Plodder::printWarning(const std::string& what,
                      int line,
                      const fs::path& filePath)
    {
        
        if(!filePath.empty()) {
            cerr << formatPath(filePath)
                << ":";

            if(line > -1)
                cerr << line << ":";
        }
        else {
            cerr << "webplodder:";
        }

        cerr << " warning: " << what << endl;
    }
}
